
#include "win32socket.h"
#include "win32window.h"
#include <cstdlib>

namespace tinkle {

#define WM_SOCKETNOTIFY      (WM_USER+100)
#define WM_DNSNOTIFY           (WM_USER+101)

struct Win32Socket::DNSLookUp {
    HANDLE handle;
    char buffer[MAXGETHOSTSTRUCT];
    int port;
};

class Win32Socket::EventSink : public Win32Window {
public:
    explicit EventSink(Win32Socket *parent) : _parent(parent){
        // blank line
    }
    ~EventSink() {
        // blank line
    }
    virtual bool OnMessage(UINT msg, WPARAM wParam, LPARAM lParam,
        LRESULT &result);
private:
    Win32Socket *_parent;
};

bool Win32Socket::EventSink::OnMessage(UINT msg, WPARAM wParam,
    LPARAM lParam, LRESULT &result) {
    result = 0;
    switch (msg) {
    case WM_SOCKETNOTIFY:
        if (_parent) {
            _parent->OnSocketNotify(lParam);
            return true;
        }
        break;
    case WM_DNSNOTIFY:
        if (_parent) {
            _parent->OnDNSNotify(wParam, lParam);
            return true;
        }
        break;
    case WM_CHAR:
        printf ("key\n");
        break;
    }
    return false;
}

Win32Socket::Win32Socket(int type)
    : _type(type),
    _state(CS_CLOSED) {
    _sink = new EventSink(this);
    _sink->Create(NULL, TEXT("Win32 socket event sink"));
}

Win32Socket::~Win32Socket() {

}

int Win32Socket::_NonTLSRead(char *buf, int len) {
    int byteCnt = recv(_sock, buf, len, 0);
    UpdateLastError();
    return byteCnt;
}

int Win32Socket::_NonTLSWrite(const char *buf, int len) {
    int byteCnt = send(_sock, buf, len, 0);
    UpdateLastError();
    return byteCnt;
}

int Win32Socket::Connect(const std::string &host, const std::string &port) {
    _addr.sin_port = htons(atoi(port.c_str()));
    if (_sock != INVALID_SOCKET) {
        // socket has been created, needn' t to connect again.
        return SOCKET_ERROR;
    }
    // create a new socket
    _sock = WSASocket(AF_UNSPEC, _type,
        (_type == SOCK_STREAM) ? IPPROTO_TCP : IPPROTO_UDP, NULL, NULL, 0);
    if (_sock == INVALID_SOCKET) {
        UpdateLastError();
        return SOCKET_ERROR;
    }
    SetAsync(FD_READ | FD_WRITE |FD_CONNECT | FD_CLOSE);
    // DNS look up
    DNSLookUp *dns = new DNSLookUp;
    dns->handle = WSAAsyncGetHostByName(_sink->Handle(), WM_DNSNOTIFY,
        host.c_str(), dns->buffer, sizeof(dns->buffer));
    if (!dns->handle) {
        UpdateLastError();
        delete dns;
        return SOCKET_ERROR;
    }
    _dns = dns;
    _dns->port = atoi(port.c_str());
    _state = CS_DNSRESOLVING;
    //printf ("Connecting...\n");
    return 0;
}

bool Win32Socket::DoConnect() {
    // we only use IPv4 here, maybe ipv6 support will be added in the future.
    int rtnVal = connect(_sock, (struct sockaddr *)&_addr,
        sizeof(struct sockaddr));
    if (rtnVal != SOCKET_ERROR) {
        _state = CS_CONNECTED;
    } else {
        rtnVal = WSAGetLastError();
        if (rtnVal == WSAEWOULDBLOCK) {
            _state = CS_CONNECTING;
        } else {
            UpdateLastError();
            return false;
        }
    }
    return true;
}

bool Win32Socket::OnSocketNotify(LPARAM lParam) {
    int event = WSAGETSELECTEVENT(lParam);
    int err = WSAGETSELECTERROR(lParam);
    switch (event) {
    case FD_CONNECT:
        if (err == ERROR_SUCCESS) {
            _state = CS_CONNECTED;
            printf ("Connected!\n");
            return true;
        }
        break;
    case FD_READ:
        printf ("FD_READ\n");
        Socket::SigRead(*this);
        break;
    case FD_WRITE:
        printf ("FD_WRITE\n");
    }
    return false;
}

bool Win32Socket::OnDNSNotify(WPARAM wParam, LPARAM lParam) {
    if (_dns->handle == (HANDLE)wParam) {
        int err = WSAGETASYNCERROR(lParam);
        if (err == 0) {
            HOSTENT *hostInfo = (PHOSTENT)_dns->buffer;
            //printf ("%s\n", hostInfo->h_name);
            //_addr.sin_family = hostInfo->h_addrtype;
            _addr.sin_family = AF_INET;
            _addr.sin_addr.s_addr = *(u_long *)hostInfo->h_addr_list[0];
            
            printf ("DNS resolved!!!\n");
            _state = CS_DNSRESOLVED;
            DoConnect();
            return true;
        } else {
            printf ("Some error DNS\n");
        }
    }
    return false;
}

bool Win32Socket::SetAsync(long events) {
    WSAAsyncSelect(_sock, _sink->Handle(), WM_SOCKETNOTIFY, events);
    return true;
}

void Win32Socket::UpdateLastError() {
    printf ("UpdateLastError:%d\n", WSAGetLastError());
}

}